/**  
* Copyright (c) 2008, Noel Lynch All rights reserved. Redistribution and use  
* in source and binary forms, with or without modification, are permitted  
* provided that the following conditions are met: Redistributions of source  
* code must retain the above copyright notice, this list of conditions and the  
* following disclaimer. Redistributions in binary form must reproduce the above  
* copyright notice, this list of conditions and the following disclaimer in the  
* documentation and/or other materials provided with the distribution. Neither  
* the name of Noel Lynch nor the names of contributors may be used to  
* endorse or promote products derived from this software without specific prior  
* written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT  
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR  
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,  
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF  
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/
// Nov 18, 2008

package com.sceneworker.app.input;

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.concurrent.Callable;

import javax.swing.tree.TreePath;

import com.acarter.scenemonitor.SceneMonitor;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.math.FastMath;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.system.DisplaySystem;
import com.jme.util.GameTaskQueueManager;
import com.jme2utils.input.CommandInputMapping;
import com.jme2utils.input.IInputCommandCallback;
import com.jme2utils.input.InputMappingTables;
import com.jme2utils.objects.InputMode;
import com.jme2utils.staticutils.CameraUtils;
import com.sceneworker.SceneWorker;
import com.sceneworker.app.StandardJMEController;
import com.sceneworker.app.StandardJMEToolsPalette;
import com.sceneworker.app.events.DelegateEventTypes;
import com.sceneworker.app.globals.SceneWorkerGlobals;
import com.sceneworker.app.globals.cacheobjects.RootNodeCacheObject;
import com.sceneworker.app.undo.GroupUndoAction;
import com.sceneworker.app.undo.SceneWorkerAppUndoManager;
import com.sceneworker.app.undo.SpatialScaleUndoAction;
import com.sceneworker.app.undo.SpatialTranslationUndoAction;
import com.sceneworker.util.delegation.DelegationEvent;
import com.sceneworker.util.delegation.DelegationEventBroadcaster;
import com.sceneworker.util.swing.radiobutton.SimpleRadioButtonManager;

/**
 * Main input handle for Scene Worker
 * @author noellynch
 *
 */
public class SceneWorkerInputHandler extends MouseAdapter {
	private static final String NEXT_TRANSFORM = "Next Transform";
	private static final String PREVIOUS_TRANSFORM = "Previous Transform";
	private static final String UNDO = "Undo";
	public static final float ROTATE_FACTOR = 0.01f;
	private static final String SWITCH_MOUSE = "switchMouse";
	
	private		BaseSelectionHandler					mcl_curSelectionHandler;
	private		StandardJMEController					mcl_appController;
	
	private		Hashtable<Class, BaseSelectionHandler>			mcl_selectionHandlers;
	
	public	SceneWorkerInputHandler(StandardJMEController cl_app) {
		mcl_selectionHandlers = new Hashtable<Class, BaseSelectionHandler>();
		mcl_selectionHandlers.put(Spatial.class, new SpatialSelectionHandler());
		
		mcl_appController = cl_app;
		mcl_curSelectionHandler = new SpatialSelectionHandler();
		
		// register this class with clicks on the tree...
		SceneMonitor.getMonitor().getMonitorTree().addMouseListener(this);		
	}
	
	/**
	 * Get the spatial selection handler
	 * @return
	 */
	public	BaseSelectionHandler		getSelectionHandler() {
		return mcl_curSelectionHandler;
	}

	/**
	 * Set the current input handler mode
	 * @param e_mode
	 */
	public	void	setInputHandlerMode(InputMode e_mode) {
		mcl_curSelectionHandler.setInputMode(e_mode);
	}
	
	/**
	 * Get the current input handler mode
	 * @return
	 */
	public	InputMode		getInputHandlerMode() {
		return mcl_curSelectionHandler.getInputMode();
	}
	
	/**
	 * Sets the input handler to have the current input focus
	 * @param b_focusOn
	 */
	public	void	setFocusOn(boolean b_focusOn) {
		mcl_curSelectionHandler.setFocusOn(b_focusOn);
	}
	
	/**
	 * Checks to see if the mouse has been flagged as down
	 * @return
	 */
	public	boolean	isMouseDown() {
		return mcl_curSelectionHandler.isMouseDown();
	}
	
	/**
	 * Processes mouse clicks from JME JTree.
	 */
	public void mouseClicked(MouseEvent e) {
		TreePath		lcl_treePath = SceneMonitor.getMonitor().getMonitorTree().getSelectionPath();
		Object			lcl_obj = lcl_treePath.getLastPathComponent();
		
		if(lcl_obj instanceof Spatial) {
			mcl_curSelectionHandler = mcl_selectionHandlers.get(Spatial.class);
			Spatial		lcl_treeSel = (Spatial)lcl_obj;
			
			// update our spatial selector
			if(lcl_treeSel != mcl_curSelectionHandler.getCurrentSelection()) {
				DelegationEventBroadcaster.inst().broadcast(new DelegationEvent(DelegateEventTypes.NEW_SPATIAL_SELECTION_EVT, this, lcl_treeSel));
				mcl_curSelectionHandler.setCurrentSelection(lcl_treeSel);
				
				if(SceneWorkerGlobals.inst().getZoomOnSelectedObjects()) {
					moveCameraToNewSelection(lcl_treeSel);
				}
			}
		}
	}
	
	protected	void	moveCameraToNewSelection(final Spatial cl_sel) {
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			public Void call() throws Exception {
				CameraUtils.moveCameraToObject(cl_sel);
				return null;
			}
		});
	}
	
	/**
	 * Move the camera to the newly selected spatial
	 */
	protected	void	moveCameraToNewSelection(final Vector3f cl_v, final float f_radius) {
		GameTaskQueueManager.getManager().update(new Callable<Void>() {
			public Void call() throws Exception {
				CameraUtils.moveCameraToPointOfInterest(cl_v, f_radius);
				return null;
			}
		});
	}
	
	/**
	 * Initialises the input handler
	 */
	public	void	initialiseInputHandler() {
		CommandInputMapping	lcl_undo = new CommandInputMapping(UNDO, false, KeyInput.KEY_U);
		lcl_undo.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				SceneWorkerAppUndoManager.inst().undo();
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_undo);
		
		CommandInputMapping	lcl_spatialSel = new CommandInputMapping(StandardJMEToolsPalette.SPATIAL_SELECTION, false, KeyInput.KEY_Q);
		lcl_spatialSel.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.setTriangleSelectMode(false);
				mcl_curSelectionHandler.setInputMode(InputMode.kObjectSelection);
				SimpleRadioButtonManager		lcl_txMgr = mcl_appController.getToolsPalette().getTransformationButtonsManager();
				lcl_txMgr.setCurrentSelectionByName(StandardJMEToolsPalette.SPATIAL_SELECTION);
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_spatialSel);
		
		CommandInputMapping	lcl_freeTx = new CommandInputMapping(StandardJMEToolsPalette.FREE_TRANSLATION, false, KeyInput.KEY_W);
		lcl_freeTx.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.setTriangleSelectMode(false);
				mcl_curSelectionHandler.setInputMode(InputMode.kObjectFreeTranslation);
				SimpleRadioButtonManager		lcl_txMgr = mcl_appController.getToolsPalette().getTransformationButtonsManager();
				lcl_txMgr.setCurrentSelectionByName(StandardJMEToolsPalette.FREE_TRANSLATION);
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_freeTx);
		
		CommandInputMapping	lcl_tx = new CommandInputMapping(StandardJMEToolsPalette.TRANSLATION, false, KeyInput.KEY_E);
		lcl_tx.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.setTriangleSelectMode(false);
				mcl_curSelectionHandler.setInputMode(InputMode.kObjectTranslationX);
				SimpleRadioButtonManager		lcl_txMgr = mcl_appController.getToolsPalette().getTransformationButtonsManager();
				lcl_txMgr.setCurrentSelectionByName(StandardJMEToolsPalette.TRANSLATION);
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_tx);
		
		CommandInputMapping	lcl_rot = new CommandInputMapping(StandardJMEToolsPalette.ROTATATION, false, KeyInput.KEY_R);
		lcl_rot.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.setTriangleSelectMode(false);
				mcl_curSelectionHandler.setInputMode(InputMode.kObjectRotateY);
				SimpleRadioButtonManager		lcl_txMgr = mcl_appController.getToolsPalette().getTransformationButtonsManager();
				lcl_txMgr.setCurrentSelectionByName(StandardJMEToolsPalette.ROTATATION);
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_rot);
		
		CommandInputMapping	lcl_scale = new CommandInputMapping(StandardJMEToolsPalette.SCALE, false, KeyInput.KEY_T);
		lcl_scale.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.setTriangleSelectMode(false);
				mcl_curSelectionHandler.setInputMode(InputMode.kObjectScaleX);
				SimpleRadioButtonManager		lcl_txMgr = mcl_appController.getToolsPalette().getTransformationButtonsManager();
				lcl_txMgr.setCurrentSelectionByName(StandardJMEToolsPalette.SCALE);
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_scale);
		
		CommandInputMapping	lcl_triSel = new CommandInputMapping(StandardJMEToolsPalette.SELECT_TRIS, false, KeyInput.KEY_Y);
		lcl_triSel.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.setTriangleSelectMode(true);
				mcl_curSelectionHandler.setInputMode(InputMode.kObjectSelection);
				SimpleRadioButtonManager		lcl_txMgr = mcl_appController.getToolsPalette().getTransformationButtonsManager();
				lcl_txMgr.setCurrentSelectionByName(StandardJMEToolsPalette.SELECT_TRIS);
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_triSel);
		
		CommandInputMapping		lcl_orbitCam = new CommandInputMapping(0, "ORBIT_CAM", KeyInput.KEY_LMENU);
		lcl_orbitCam.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.orbitCamera();
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_orbitCam);
		
		CommandInputMapping		lcl_zoomCam = new CommandInputMapping(1, "ZOOM_CAM", KeyInput.KEY_LMENU);
		lcl_zoomCam.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.zoomCamera();
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_zoomCam);
		
		CommandInputMapping		lcl_panCam = new CommandInputMapping(0, "PAN_CAM", KeyInput.KEY_LSHIFT);
		lcl_panCam.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				CameraUtils.panCamera(DisplaySystem.getDisplaySystem().getRenderer().getCamera());
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_panCam);
		
		CommandInputMapping	lcl_leftClick = new CommandInputMapping(0, "LEFT_CLICK");
		lcl_leftClick.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				mcl_curSelectionHandler.handleLeftMouseDown((Node)SceneWorkerGlobals.inst().getFromCache(RootNodeCacheObject.ROOT_NODE).getValue(), mcl_appController.getRenderer().getCurrentAxesRep());
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_leftClick);
		
		/*CommandInputMapping	lcl_rotateAbout = new CommandInputMapping(1, "ROTATE_ABOUT");
		lcl_rotateAbout.addInputMappingCallback(new IInputCommandCallback() {
			public void handleCommand(CommandInputMapping clCommand) {
				if(mcl_selected.getCurrentSelection() != null) {
					mb_mouseAlreadyDown = true;
					mcl_selected.rotateCamAboutSpatial();
				}
			}
		});
		InputMappingTables.getDefaultMappingContext().addCommand(lcl_rotateAbout);*/
	}
	
	/**
	 * Update mouse and keys
	 * @param cl_rootNode
	 * @param cl_ip
	 */
	public	void	updateInputHandler(Spatial cl_rootNode) {
		if(!SceneWorker.inst().areToolsEnabled()) {
			return;
		}
		
		CommandInputMapping		lcl_curCommand = InputMappingTables.getDefaultMappingContext().getOccuringCommand();
		if(lcl_curCommand != null) {
			lcl_curCommand.executeCommand();
			return;
		}
		
		mcl_curSelectionHandler.setMouseAlreadyDown(false);
	}
	
	
	
}